{ ============================================================================

  koda_ksp_probe.txt - KODA Kontakt migration probe (KSP, not Lua)

  Dumps everything Kontakt's ENGINE will tell a script about the instrument
  it is loaded into: group settings, release-trigger flags, every modulator
  slot with its type and parameters, every modulation target's intensity,
  the effect chains, and the sixteen instrument buses.

  Why this exists: NI's Lua API - the one koda_kontakt_export.lua uses - has
  no engine-parameter surface at all. Its Group chapter exposes exactly seven
  getters (name, pan, playback mode, start options, tune, volume, voice
  group). Envelopes, modulators, filters and release-trigger flags are
  invisible to it. KSP can read all of them, and save_array_str() can write
  a file. So this script is the only door.

  ---------------------------------------------------------------------------
  HOW TO RUN
  ---------------------------------------------------------------------------
  Normally you do not: koda_kontakt_export.lua installs this file into a free
  script slot itself, with the @OUT line below rewritten, reads the report
  before it writes the XML, and clears the slot again once the report has
  been used. If its log says the probe could not be installed, paste by hand:

  1. Load the instrument and open it in edit mode (the wrench).
  2. Open the Script Editor, pick a FREE script slot, paste THE WHOLE FILE -
     header comment included. Kontakt numbers lines from the start of what you
     pasted, so pasting from "on init" instead shifts every line number in an
     error message by 67 and makes the fix-one-line rule below harder to use.
  3. Edit the @OUT line below so it points at koda_ksp_probe.nka BESIDE
     koda_kontakt_export.lua. Forward slashes only - Kontakt rejects
     backslashes - and the folder must already exist.
  4. Press Apply. The status line at the bottom reports the record count and
     the path.
  5. Run the export again; it picks the report up. Then remove the script
     from the slot.

  DO NOT SAVE THE INSTRUMENT while this script is in a slot. It only ever
  reads, but a script sitting in a slot is itself a change to the patch.

  ---------------------------------------------------------------------------
  IF IT DOES NOT COMPILE
  ---------------------------------------------------------------------------
  Kontakt names the offending line. Every parameter that is NOT confirmed in
  NI's published engine-parameter list sits ALONE on its own line, tagged
  RISK-xx in a trailing comment, and is preceded by a line that pre-loads a
  -999 sentinel. So:

      delete the single tagged line, press Apply again, and tell me which
      tag you deleted.

  The record for it then reads -999 or "?" instead of a value, and nothing
  else in the run is affected.

  ---------------------------------------------------------------------------
  OUTPUT FORMAT - one pipe-delimited record per line
  ---------------------------------------------------------------------------
    KODA-KSP-PROBE|<format version>
    INFO |<key>|<value>
    INST |<param>|<raw>|<display>
    G    |<group>|<param>|<raw>|<display>
    C    |<group>|<criterion>|<param>|<raw>          group start criteria
    M    |<group>|<slot>|<param>|<raw>|<display>     modulator slot
    T    |<group>|<slot>|<target>|<param>|<raw>|<display>   (off: $DUMP_TARGETS)
    FX   |<scope>|<slot>|<param>|<raw>|<display>
    BUS  |<bus>|<param>|<raw>|<display>
    N    |<group>|<name>|<index>                     modulator name probe
    CONST|<constant name>|<its integer value>
    S    |group|mod|target|<param id>|<raw>|<display> numeric sweep (off: $SWEEP)
    END  |<record count>

  Raw engine values are 0..1000000 on non-linear laws, which is why every
  record carries the DISPLAY string too - that is the one to parse.

  The CONST and S records are what make the next question cheap: engine
  parameters are just integers behind their names, so once their IDs are known,
  anything can be read by NUMBER - and an unknown number costs nothing, while
  an unknown NAME is a compile error that kills the whole run.

  Requires Kontakt 7.1 or newer (get_mod_idx).

============================================================================ }

on init
    set_script_title("KODA probe")

    { Sweep limits. Lower them if the script is slow or the report is cut
      off; the END record says how many lines were actually written. }
    declare const $MAX_LINES := 24000
    declare const $MAX_MODS  := 32
    declare const $MAX_TGTS  := 8
    declare const $MAX_FX    := 8
    declare const $MAX_BUS   := 16
    declare const $MAX_CRIT  := 4
    declare const $MAX_STEPS := 32
    declare const $NAMES     := 40
    declare const $TNAMES    := 6
    { How far up the engine-parameter ID space the numeric sweep walks. }
    declare const $MAX_SWEEP := 1024

    { Research switches, 0 = off. The T records (target-strip intensity)
      answer "100.0" for every strip whether it exists or not, and the
      numeric sweep exists to identify undocumented IDs by diffing two
      reports. Neither feeds the exporter, and together they were 4100 of
      River Harp's 6500 lines. Off, MAX_LINES holds roughly 350 groups.

      MAX_LINES was 12000, about 175 groups, and instruments do run past
      that: the sweep stops writing and the END record says fewer lines
      than the instrument has. The array is declared at init and
      save_array_str writes ALL of it, empty slots included, so the cost of
      the headroom is a slightly larger .nka (tens of KB) and a little more
      time per save, on every run. }
    declare const $DUMP_TARGETS := 0
    declare const $SWEEP := 0

    declare !out[$MAX_LINES]
    declare !nm[$NAMES]
    declare !tn[$TNAMES]

    declare $n
    declare $g
    declare $m
    declare $t
    declare $s
    declare $b
    declare $i
    declare $v
    declare $ty
    declare @l
    declare @d
    declare @OUT

    { ======================= EDIT THIS ONE LINE =======================
      Forward slashes only. The folder must already exist - Kontakt will
      create the file but not the folder. }
    @OUT := "PASTE_THE_FULL_PATH_HERE/koda_ksp_probe.nka"
    { ================================================================== }

    $n := 0

    { ---------------------------------------------------------------- }
    { Header                                                            }
    { ---------------------------------------------------------------- }
    @l := "KODA-KSP-PROBE|2"
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "INFO|patch|" & get_folder($GET_FOLDER_PATCH_DIR)
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "INFO|groups|" & $NUM_GROUPS
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "INFO|zones|" & $NUM_ZONES
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "INFO|uptime|" & $ENGINE_UPTIME
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    { ---------------------------------------------------------------- }
    { Instrument level (group -1, slot -1, generic -1)                  }
    { ---------------------------------------------------------------- }
    $v := get_engine_par($ENGINE_PAR_VOLUME, -1, -1, -1)
    @d := ""
    @d := get_engine_par_disp($ENGINE_PAR_VOLUME, -1, -1, -1)
    @l := "INST|volume|" & $v & "|" & @d
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := get_engine_par($ENGINE_PAR_PAN, -1, -1, -1)
    @d := ""
    @d := get_engine_par_disp($ENGINE_PAR_PAN, -1, -1, -1)
    @l := "INST|pan|" & $v & "|" & @d
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := get_engine_par($ENGINE_PAR_TUNE, -1, -1, -1)
    @d := ""
    @d := get_engine_par_disp($ENGINE_PAR_TUNE, -1, -1, -1)
    @l := "INST|tune|" & $v & "|" & @d
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    { ---------------------------------------------------------------- }
    { Per group                                                         }
    { ---------------------------------------------------------------- }
    $g := 0
    while ($g < $NUM_GROUPS)

        @l := "G|" & $g & "|name|" & group_name($g)
        if ($n < $MAX_LINES)
            !out[$n] := @l
            inc($n)
        end if

        $v := get_engine_par($ENGINE_PAR_VOLUME, $g, -1, -1)
        @d := ""
        @d := get_engine_par_disp($ENGINE_PAR_VOLUME, $g, -1, -1)
        @l := "G|" & $g & "|volume|" & $v & "|" & @d
        if ($n < $MAX_LINES)
            !out[$n] := @l
            inc($n)
        end if

        $v := get_engine_par($ENGINE_PAR_PAN, $g, -1, -1)
        @d := ""
        @d := get_engine_par_disp($ENGINE_PAR_PAN, $g, -1, -1)
        @l := "G|" & $g & "|pan|" & $v & "|" & @d
        if ($n < $MAX_LINES)
            !out[$n] := @l
            inc($n)
        end if

        $v := get_engine_par($ENGINE_PAR_TUNE, $g, -1, -1)
        @d := ""
        @d := get_engine_par_disp($ENGINE_PAR_TUNE, $g, -1, -1)
        @l := "G|" & $g & "|tune|" & $v & "|" & @d
        if ($n < $MAX_LINES)
            !out[$n] := @l
            inc($n)
        end if

        $v := get_engine_par($ENGINE_PAR_OUTPUT_CHANNEL, $g, -1, -1)
        @d := ""
        @d := get_engine_par_disp($ENGINE_PAR_OUTPUT_CHANNEL, $g, -1, -1)
        @l := "G|" & $g & "|output|" & $v & "|" & @d
        if ($n < $MAX_LINES)
            !out[$n] := @l
            inc($n)
        end if

        $v := get_engine_par($ENGINE_PAR_VOICE_GROUP, $g, -1, -1)
        @l := "G|" & $g & "|voice_group|" & $v
        if ($n < $MAX_LINES)
            !out[$n] := @l
            inc($n)
        end if

        $v := get_engine_par($ENGINE_PAR_PHASE_INVERT, $g, -1, -1)
        @l := "G|" & $g & "|phase_invert|" & $v
        if ($n < $MAX_LINES)
            !out[$n] := @l
            inc($n)
        end if

        $v := get_engine_par($ENGINE_PAR_LR_SWAP, $g, -1, -1)
        @l := "G|" & $g & "|lr_swap|" & $v
        if ($n < $MAX_LINES)
            !out[$n] := @l
            inc($n)
        end if

        { The prize: ends the guessing of release triggers from group names.
          Not in NI's published parameter list, but used widely in community
          KSP. If it will not compile, delete the single tagged line. }
        $v := -999
        $v := get_engine_par($ENGINE_PAR_RELEASE_TRIGGER, $g, -1, -1)   { RISK-RT }
        @l := "G|" & $g & "|release_trigger|" & $v
        if ($n < $MAX_LINES)
            !out[$n] := @l
            inc($n)
        end if

        { Source module mode: sampler / DFD / time machine / tone machine. }
        $v := -999
        $v := get_engine_par($ENGINE_PAR_SOURCE_MODE, $g, -1, -1)        { RISK-SRC }
        @l := "G|" & $g & "|source_mode|" & $v
        if ($n < $MAX_LINES)
            !out[$n] := @l
            inc($n)
        end if

        { -------- group start criteria (round robin lives here) -------- }
        $s := 0
        while ($s < $MAX_CRIT)
            $v := get_engine_par($ENGINE_PAR_START_CRITERIA_MODE, $g, $s, -1)
            if ($v # $START_CRITERIA_NONE)
                @l := "C|" & $g & "|" & $s & "|mode|" & $v
                if ($n < $MAX_LINES)
                    !out[$n] := @l
                    inc($n)
                end if

                $v := get_engine_par($ENGINE_PAR_START_CRITERIA_KEY_MIN, $g, $s, -1)
                @l := "C|" & $g & "|" & $s & "|key_min|" & $v
                if ($n < $MAX_LINES)
                    !out[$n] := @l
                    inc($n)
                end if

                $v := get_engine_par($ENGINE_PAR_START_CRITERIA_KEY_MAX, $g, $s, -1)
                @l := "C|" & $g & "|" & $s & "|key_max|" & $v
                if ($n < $MAX_LINES)
                    !out[$n] := @l
                    inc($n)
                end if

                $v := get_engine_par($ENGINE_PAR_START_CRITERIA_CONTROLLER, $g, $s, -1)
                @l := "C|" & $g & "|" & $s & "|controller|" & $v
                if ($n < $MAX_LINES)
                    !out[$n] := @l
                    inc($n)
                end if

                $v := get_engine_par($ENGINE_PAR_START_CRITERIA_CC_MIN, $g, $s, -1)
                @l := "C|" & $g & "|" & $s & "|cc_min|" & $v
                if ($n < $MAX_LINES)
                    !out[$n] := @l
                    inc($n)
                end if

                $v := get_engine_par($ENGINE_PAR_START_CRITERIA_CC_MAX, $g, $s, -1)
                @l := "C|" & $g & "|" & $s & "|cc_max|" & $v
                if ($n < $MAX_LINES)
                    !out[$n] := @l
                    inc($n)
                end if

                $v := get_engine_par($ENGINE_PAR_START_CRITERIA_CYCLE_CLASS, $g, $s, -1)
                @l := "C|" & $g & "|" & $s & "|cycle_class|" & $v
                if ($n < $MAX_LINES)
                    !out[$n] := @l
                    inc($n)
                end if

                $v := get_engine_par($ENGINE_PAR_START_CRITERIA_SEQ_ONLY, $g, $s, -1)
                @l := "C|" & $g & "|" & $s & "|seq_only|" & $v
                if ($n < $MAX_LINES)
                    !out[$n] := @l
                    inc($n)
                end if

                $v := get_engine_par($ENGINE_PAR_START_CRITERIA_NEXT_CRIT, $g, $s, -1)
                @l := "C|" & $g & "|" & $s & "|next_crit|" & $v
                if ($n < $MAX_LINES)
                    !out[$n] := @l
                    inc($n)
                end if
            end if
            inc($s)
        end while

        { -------- modulator slots, enumerated BY INDEX -------- }
        { $ENGINE_PAR_INTMOD_TYPE is what makes this possible: no need to
          guess a modulator's name, the slot itself reports what it holds. }
        $m := 0
        while ($m < $MAX_MODS)
            $ty := get_engine_par($ENGINE_PAR_INTMOD_TYPE, $g, $m, -1)
            if ($ty # $INTMOD_TYPE_NONE)

                @l := "M|" & $g & "|" & $m & "|type|" & $ty
                if ($n < $MAX_LINES)
                    !out[$n] := @l
                    inc($n)
                end if

                $v := get_engine_par($ENGINE_PAR_INTMOD_SUBTYPE, $g, $m, -1)
                @l := "M|" & $g & "|" & $m & "|subtype|" & $v
                if ($n < $MAX_LINES)
                    !out[$n] := @l
                    inc($n)
                end if

                { ---- envelopes: AHDSR, DBD or Flex ---- }
                if ($ty = $INTMOD_TYPE_ENVELOPE)
                    $v := get_engine_par($ENGINE_PAR_ATTACK, $g, $m, -1)
                    @d := ""
                    @d := get_engine_par_disp($ENGINE_PAR_ATTACK, $g, $m, -1)
                    @l := "M|" & $g & "|" & $m & "|attack|" & $v & "|" & @d
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    $v := get_engine_par($ENGINE_PAR_HOLD, $g, $m, -1)
                    @d := ""
                    @d := get_engine_par_disp($ENGINE_PAR_HOLD, $g, $m, -1)
                    @l := "M|" & $g & "|" & $m & "|hold|" & $v & "|" & @d
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    $v := get_engine_par($ENGINE_PAR_DECAY, $g, $m, -1)
                    @d := ""
                    @d := get_engine_par_disp($ENGINE_PAR_DECAY, $g, $m, -1)
                    @l := "M|" & $g & "|" & $m & "|decay|" & $v & "|" & @d
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    $v := get_engine_par($ENGINE_PAR_SUSTAIN, $g, $m, -1)
                    @d := ""
                    @d := get_engine_par_disp($ENGINE_PAR_SUSTAIN, $g, $m, -1)
                    @l := "M|" & $g & "|" & $m & "|sustain|" & $v & "|" & @d
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    $v := get_engine_par($ENGINE_PAR_RELEASE, $g, $m, -1)
                    @d := ""
                    @d := get_engine_par_disp($ENGINE_PAR_RELEASE, $g, $m, -1)
                    @l := "M|" & $g & "|" & $m & "|release|" & $v & "|" & @d
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    $v := -999
                    $v := get_engine_par($ENGINE_PAR_ATK_CURVE, $g, $m, -1)   { RISK-CURVE }
                    @d := ""
                    @d := get_engine_par_disp($ENGINE_PAR_ATK_CURVE, $g, $m, -1)   { RISK-CURVE }
                    @l := "M|" & $g & "|" & $m & "|atk_curve|" & $v & "|" & @d
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    $v := -999
                    $v := get_engine_par($ENGINE_PAR_ENV_AHD, $g, $m, -1)     { RISK-AHD }
                    @l := "M|" & $g & "|" & $m & "|ahd_only|" & $v
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    { DBD envelopes: these read as garbage on an AHDSR, which
                      is fine - the subtype record says which one this is. }
                    $v := -999
                    $v := get_engine_par($ENGINE_PAR_DECAY1, $g, $m, -1)      { RISK-DBD }
                    @l := "M|" & $g & "|" & $m & "|decay1|" & $v
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    $v := -999
                    $v := get_engine_par($ENGINE_PAR_BREAK, $g, $m, -1)       { RISK-DBD }
                    @l := "M|" & $g & "|" & $m & "|break|" & $v
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    $v := -999
                    $v := get_engine_par($ENGINE_PAR_DECAY2, $g, $m, -1)      { RISK-DBD }
                    @l := "M|" & $g & "|" & $m & "|decay2|" & $v
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if
                end if

                { ---- LFOs ---- }
                if ($ty = $INTMOD_TYPE_LFO)
                    $v := get_engine_par($ENGINE_PAR_INTMOD_FREQUENCY, $g, $m, -1)
                    @d := ""
                    @d := get_engine_par_disp($ENGINE_PAR_INTMOD_FREQUENCY, $g, $m, -1)
                    @l := "M|" & $g & "|" & $m & "|frequency|" & $v & "|" & @d
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    $v := get_engine_par($ENGINE_PAR_LFO_DELAY, $g, $m, -1)
                    @d := ""
                    @d := get_engine_par_disp($ENGINE_PAR_LFO_DELAY, $g, $m, -1)
                    @l := "M|" & $g & "|" & $m & "|lfo_delay|" & $v & "|" & @d
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    $v := get_engine_par($ENGINE_PAR_LFO_PHASE, $g, $m, -1)
                    @d := ""
                    @d := get_engine_par_disp($ENGINE_PAR_LFO_PHASE, $g, $m, -1)
                    @l := "M|" & $g & "|" & $m & "|lfo_phase|" & $v & "|" & @d
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    $v := -999
                    $v := get_engine_par($ENGINE_PAR_INTMOD_PULSEWIDTH, $g, $m, -1)  { RISK-PW }
                    @l := "M|" & $g & "|" & $m & "|pulsewidth|" & $v
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if
                end if

                { ---- step modulator ---- }
                if ($ty = $INTMOD_TYPE_STEPMOD)
                    $v := get_engine_par($ENGINE_PAR_INTMOD_FREQUENCY, $g, $m, -1)
                    @d := ""
                    @d := get_engine_par_disp($ENGINE_PAR_INTMOD_FREQUENCY, $g, $m, -1)
                    @l := "M|" & $g & "|" & $m & "|frequency|" & $v & "|" & @d
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    $s := -999
                    $s := get_engine_par($ENGINE_PAR_STEPSEQ_NUM_STEPS, $g, $m, -1)  { RISK-STEP }
                    @l := "M|" & $g & "|" & $m & "|num_steps|" & $s
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    $v := -999
                    $v := get_engine_par($ENGINE_PAR_STEPSEQ_ONESHOT, $g, $m, -1)    { RISK-STEP }
                    @l := "M|" & $g & "|" & $m & "|oneshot|" & $v
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    { Step values: the step index goes in the generic slot. }
                    $i := 0
                    while ($i < $MAX_STEPS)
                        $v := -999
                        $v := get_engine_par($ENGINE_PAR_STEPSEQ_STEP_VALUE, $g, $m, $i)  { RISK-STEP }
                        @l := "M|" & $g & "|" & $m & "|step_" & $i & "|" & $v
                        if ($n < $MAX_LINES)
                            !out[$n] := @l
                            inc($n)
                        end if
                        inc($i)
                    end while
                end if

                { ---- what this modulator is wired to - research only ---- }
                if ($DUMP_TARGETS = 1)
                $t := 0
                while ($t < $MAX_TGTS)
                    $v := get_engine_par($ENGINE_PAR_MOD_TARGET_INTENSITY, $g, $m, $t)
                    @d := ""
                    @d := get_engine_par_disp($ENGINE_PAR_MOD_TARGET_INTENSITY, $g, $m, $t)
                    @l := "T|" & $g & "|" & $m & "|" & $t & "|intensity|" & $v & "|" & @d
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    $v := -999
                    $v := get_engine_par($ENGINE_PAR_MOD_TARGET_MP_INTENSITY, $g, $m, $t)  { RISK-MP }
                    @l := "T|" & $g & "|" & $m & "|" & $t & "|mp_intensity|" & $v
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if

                    { The invert-source flag used to be read here.
                      $ENGINE_PAR_MOD_TARGET_INVERT_SOURCE does not exist on
                      Kontakt 8.13.0 ("was not declared"), and the shorter
                      spelling NI also prints is just as unverified - so it is
                      not a named constant any more. The numeric sweep at the
                      foot of this script finds it by its ID instead. }

                    inc($t)
                end while
                end if
            end if
            inc($m)
        end while

        { -------- group insert FX chain -------- }
        $s := 0
        while ($s < $MAX_FX)
            $v := get_engine_par($ENGINE_PAR_EFFECT_TYPE, $g, $s, -1)
            if ($v # $EFFECT_TYPE_NONE)
                @l := "FX|group" & $g & "|" & $s & "|type|" & $v
                if ($n < $MAX_LINES)
                    !out[$n] := @l
                    inc($n)
                end if

                $v := get_engine_par($ENGINE_PAR_EFFECT_SUBTYPE, $g, $s, -1)
                @l := "FX|group" & $g & "|" & $s & "|subtype|" & $v
                if ($n < $MAX_LINES)
                    !out[$n] := @l
                    inc($n)
                end if

                $v := get_engine_par($ENGINE_PAR_EFFECT_BYPASS, $g, $s, -1)
                @l := "FX|group" & $g & "|" & $s & "|bypass|" & $v
                if ($n < $MAX_LINES)
                    !out[$n] := @l
                    inc($n)
                end if

                $v := get_engine_par($ENGINE_PAR_CUTOFF, $g, $s, -1)
                @d := ""
                @d := get_engine_par_disp($ENGINE_PAR_CUTOFF, $g, $s, -1)
                @l := "FX|group" & $g & "|" & $s & "|cutoff|" & $v & "|" & @d
                if ($n < $MAX_LINES)
                    !out[$n] := @l
                    inc($n)
                end if

                $v := get_engine_par($ENGINE_PAR_RESONANCE, $g, $s, -1)
                @d := ""
                @d := get_engine_par_disp($ENGINE_PAR_RESONANCE, $g, $s, -1)
                @l := "FX|group" & $g & "|" & $s & "|resonance|" & $v & "|" & @d
                if ($n < $MAX_LINES)
                    !out[$n] := @l
                    inc($n)
                end if
            end if
            inc($s)
        end while

        inc($g)
    end while

    { ---------------------------------------------------------------- }
    { Instrument insert FX and main FX                                  }
    { ---------------------------------------------------------------- }
    $s := 0
    while ($s < $MAX_FX)
        $v := get_engine_par($ENGINE_PAR_EFFECT_TYPE, -1, $s, $NI_INSERT_BUS)
        if ($v # $EFFECT_TYPE_NONE)
            @l := "FX|insert|" & $s & "|type|" & $v
            if ($n < $MAX_LINES)
                !out[$n] := @l
                inc($n)
            end if

            $v := get_engine_par($ENGINE_PAR_EFFECT_SUBTYPE, -1, $s, $NI_INSERT_BUS)
            @l := "FX|insert|" & $s & "|subtype|" & $v
            if ($n < $MAX_LINES)
                !out[$n] := @l
                inc($n)
            end if
        end if

        $v := get_engine_par($ENGINE_PAR_EFFECT_TYPE, -1, $s, $NI_MAIN_BUS)
        if ($v # $EFFECT_TYPE_NONE)
            @l := "FX|main|" & $s & "|type|" & $v
            if ($n < $MAX_LINES)
                !out[$n] := @l
                inc($n)
            end if
        end if

        $v := get_engine_par($ENGINE_PAR_SEND_EFFECT_TYPE, -1, $s, $NI_SEND_BUS)
        if ($v # $EFFECT_TYPE_NONE)
            @l := "FX|send|" & $s & "|type|" & $v
            if ($n < $MAX_LINES)
                !out[$n] := @l
                inc($n)
            end if
        end if

        inc($s)
    end while

    { ---------------------------------------------------------------- }
    { The sixteen instrument buses - where mic-position mixes usually   }
    { live, and the natural home for KODA's input buses.                }
    { ---------------------------------------------------------------- }
    $b := 0
    while ($b < $MAX_BUS)
        $v := get_engine_par($ENGINE_PAR_VOLUME, -1, -1, $NI_BUS_OFFSET + $b)
        @d := ""
        @d := get_engine_par_disp($ENGINE_PAR_VOLUME, -1, -1, $NI_BUS_OFFSET + $b)
        @l := "BUS|" & $b & "|volume|" & $v & "|" & @d
        if ($n < $MAX_LINES)
            !out[$n] := @l
            inc($n)
        end if

        $v := get_engine_par($ENGINE_PAR_PAN, -1, -1, $NI_BUS_OFFSET + $b)
        @d := ""
        @d := get_engine_par_disp($ENGINE_PAR_PAN, -1, -1, $NI_BUS_OFFSET + $b)
        @l := "BUS|" & $b & "|pan|" & $v & "|" & @d
        if ($n < $MAX_LINES)
            !out[$n] := @l
            inc($n)
        end if

        $v := get_engine_par($ENGINE_PAR_OUTPUT_CHANNEL, -1, -1, $NI_BUS_OFFSET + $b)
        @l := "BUS|" & $b & "|output|" & $v
        if ($n < $MAX_LINES)
            !out[$n] := @l
            inc($n)
        end if

        inc($b)
    end while

    { ---------------------------------------------------------------- }
    { First save - before the riskiest pass, so a hang there still      }
    { leaves a usable report on disk.                                   }
    { ---------------------------------------------------------------- }
    !out[$MAX_LINES - 1] := "END|" & $n
    save_array_str(!out, @OUT)

    { ---------------------------------------------------------------- }
    { Name probe. Modulators also carry a predefined name built from    }
    { their type and target ("VEL_VOLUME", "ENV_AHDSR_CUTOFF"), and     }
    { get_mod_idx maps a name to the slot index. Going the other way -  }
    { index to name - is not in the API, so this guesses the common     }
    { ones to learn the convention and, in particular, to find out      }
    { whether Kontakt's release-trigger counter is addressable.         }
    { ---------------------------------------------------------------- }
    !nm[0]  := "VOL_ENV"
    !nm[1]  := "FILTER_ENV"
    !nm[2]  := "PITCH_ENV"
    !nm[3]  := "VEL_VOLUME"
    !nm[4]  := "VEL_CUTOFF"
    !nm[5]  := "VEL_PITCH"
    !nm[6]  := "VEL_PAN"
    !nm[7]  := "VEL_SAMPLE_START"
    !nm[8]  := "ENV_AHDSR_VOLUME"
    !nm[9]  := "ENV_AHDSR_CUTOFF"
    !nm[10] := "ENV_AHDSR_PITCH"
    !nm[11] := "ENV_AHDSR_PAN"
    !nm[12] := "ENV_DBD_VOLUME"
    !nm[13] := "ENV_FLEX_VOLUME"
    !nm[14] := "LFO_SINE_VOLUME"
    !nm[15] := "LFO_SINE_PITCH"
    !nm[16] := "LFO_SINE_CUTOFF"
    !nm[17] := "LFO_TRIANGLE_PITCH"
    !nm[18] := "LFO_TRIANGLE_VOLUME"
    !nm[19] := "LFO_RECTANGLE_VOLUME"
    !nm[20] := "LFO_SAWTOOTH_PITCH"
    !nm[21] := "LFO_RANDOM_PITCH"
    !nm[22] := "LFO_MULTI_VOLUME"
    !nm[23] := "STEPMOD_VOLUME"
    !nm[24] := "STEPMOD_CUTOFF"
    !nm[25] := "GLIDE_PITCH"
    !nm[26] := "ENV_FOLLOW_VOLUME"
    !nm[27] := "KEY_VOLUME"
    !nm[28] := "KEY_CUTOFF"
    !nm[29] := "KEY_PITCH"
    !nm[30] := "REL_TRIG_COUNTER"
    !nm[31] := "REL_TRIG_COUNTER_VOLUME"
    !nm[32] := "RELEASE_TRIGGER_COUNTER"
    !nm[33] := "RLS_TRIG_COUNTER_VOLUME"
    !nm[34] := "MODWHEEL_VOLUME"
    !nm[35] := "AFTERTOUCH_VOLUME"
    !nm[36] := "PITCHBEND_PITCH"
    !nm[37] := "CC_VOLUME"
    !nm[38] := "EXT_MOD_VOLUME"
    !nm[39] := "AMP_ENV"

    $g := 0
    while ($g < $NUM_GROUPS)
        $i := 0
        while ($i < $NAMES)
            $v := -999
            $v := get_mod_idx($g, !nm[$i])          { RISK-NAME }
            { $NI_NOT_FOUND is -100 on 8.13, not -1: comparing against -1 made
              every miss look like a hit. A real slot index is 0 or greater. }
            if ($v > -1)
                if ($v # -999)
                    @l := "N|" & $g & "|" & !nm[$i] & "|" & $v
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if
                end if
            end if
            inc($i)
        end while
        inc($g)
    end while

    { ---------------------------------------------------------------- }
    { Target names.                                                     }
    {                                                                   }
    { Every target strip answers "100.0" whether or not it is really    }
    { there - run 1 showed all eight reporting full intensity on a      }
    { modulator that plainly has one target - so intensity cannot say   }
    { how many targets exist. get_target_idx can: a name that resolves  }
    { to an index is proof of a strip, and names the destination while  }
    { it is at it.                                                      }
    { ---------------------------------------------------------------- }
    !tn[0] := "VOLUME"
    !tn[1] := "CUTOFF"
    !tn[2] := "PITCH"
    !tn[3] := "PAN"
    !tn[4] := "TUNE"
    !tn[5] := "SAMPLE_START"

    $g := 0
    while ($g < $NUM_GROUPS)
        $m := 0
        while ($m < $MAX_MODS)
            $i := 0
            while ($i < $TNAMES)
                $v := -999
                $v := get_target_idx($g, $m, !tn[$i])   { RISK-TGT }
                if ($v > -1)
                    @l := "TN|" & $g & "|" & $m & "|" & !tn[$i] & "|" & $v
                    if ($n < $MAX_LINES)
                        !out[$n] := @l
                        inc($n)
                    end if
                end if
                inc($i)
            end while
            inc($m)
        end while
        inc($g)
    end while

    { ---------------------------------------------------------------- }
    { What every constant is actually WORTH.                            }
    {                                                                   }
    { KSP engine parameters are plain integers behind the names, and
      get_engine_par takes the number. Recording the numbers turns every
      later question from 'guess the spelling' - which costs a whole
      session when it is wrong - into 'read the ID', which costs nothing.
      It is also the key to the sweep below. }
    { ---------------------------------------------------------------- }
    @l := "CONST|ENGINE_PAR_VOLUME|" & $ENGINE_PAR_VOLUME
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_PAN|" & $ENGINE_PAR_PAN
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_TUNE|" & $ENGINE_PAR_TUNE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_OUTPUT_CHANNEL|" & $ENGINE_PAR_OUTPUT_CHANNEL
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_VOICE_GROUP|" & $ENGINE_PAR_VOICE_GROUP
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_PHASE_INVERT|" & $ENGINE_PAR_PHASE_INVERT
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_LR_SWAP|" & $ENGINE_PAR_LR_SWAP
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_RELEASE_TRIGGER|" & $ENGINE_PAR_RELEASE_TRIGGER
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_SOURCE_MODE|" & $ENGINE_PAR_SOURCE_MODE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_START_CRITERIA_MODE|" & $ENGINE_PAR_START_CRITERIA_MODE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_START_CRITERIA_KEY_MIN|" & $ENGINE_PAR_START_CRITERIA_KEY_MIN
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_START_CRITERIA_KEY_MAX|" & $ENGINE_PAR_START_CRITERIA_KEY_MAX
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_START_CRITERIA_CONTROLLER|" & $ENGINE_PAR_START_CRITERIA_CONTROLLER
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_START_CRITERIA_CC_MIN|" & $ENGINE_PAR_START_CRITERIA_CC_MIN
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_START_CRITERIA_CC_MAX|" & $ENGINE_PAR_START_CRITERIA_CC_MAX
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_START_CRITERIA_CYCLE_CLASS|" & $ENGINE_PAR_START_CRITERIA_CYCLE_CLASS
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_START_CRITERIA_SEQ_ONLY|" & $ENGINE_PAR_START_CRITERIA_SEQ_ONLY
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_START_CRITERIA_NEXT_CRIT|" & $ENGINE_PAR_START_CRITERIA_NEXT_CRIT
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_INTMOD_TYPE|" & $ENGINE_PAR_INTMOD_TYPE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_INTMOD_SUBTYPE|" & $ENGINE_PAR_INTMOD_SUBTYPE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_ATTACK|" & $ENGINE_PAR_ATTACK
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_HOLD|" & $ENGINE_PAR_HOLD
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_DECAY|" & $ENGINE_PAR_DECAY
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_SUSTAIN|" & $ENGINE_PAR_SUSTAIN
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_RELEASE|" & $ENGINE_PAR_RELEASE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_ATK_CURVE|" & $ENGINE_PAR_ATK_CURVE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_ENV_AHD|" & $ENGINE_PAR_ENV_AHD
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_DECAY1|" & $ENGINE_PAR_DECAY1
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_BREAK|" & $ENGINE_PAR_BREAK
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_DECAY2|" & $ENGINE_PAR_DECAY2
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_INTMOD_FREQUENCY|" & $ENGINE_PAR_INTMOD_FREQUENCY
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_LFO_DELAY|" & $ENGINE_PAR_LFO_DELAY
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_LFO_PHASE|" & $ENGINE_PAR_LFO_PHASE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_INTMOD_PULSEWIDTH|" & $ENGINE_PAR_INTMOD_PULSEWIDTH
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_STEPSEQ_NUM_STEPS|" & $ENGINE_PAR_STEPSEQ_NUM_STEPS
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_STEPSEQ_ONESHOT|" & $ENGINE_PAR_STEPSEQ_ONESHOT
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_STEPSEQ_STEP_VALUE|" & $ENGINE_PAR_STEPSEQ_STEP_VALUE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_MOD_TARGET_INTENSITY|" & $ENGINE_PAR_MOD_TARGET_INTENSITY
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_MOD_TARGET_MP_INTENSITY|" & $ENGINE_PAR_MOD_TARGET_MP_INTENSITY
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|INTMOD_TYPE_NONE|" & $INTMOD_TYPE_NONE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|INTMOD_TYPE_ENVELOPE|" & $INTMOD_TYPE_ENVELOPE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|INTMOD_TYPE_LFO|" & $INTMOD_TYPE_LFO
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|INTMOD_TYPE_STEPMOD|" & $INTMOD_TYPE_STEPMOD
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|START_CRITERIA_NONE|" & $START_CRITERIA_NONE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|EFFECT_TYPE_NONE|" & $EFFECT_TYPE_NONE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_EFFECT_TYPE|" & $ENGINE_PAR_EFFECT_TYPE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_EFFECT_SUBTYPE|" & $ENGINE_PAR_EFFECT_SUBTYPE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_EFFECT_BYPASS|" & $ENGINE_PAR_EFFECT_BYPASS
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_SEND_EFFECT_TYPE|" & $ENGINE_PAR_SEND_EFFECT_TYPE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_CUTOFF|" & $ENGINE_PAR_CUTOFF
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|ENGINE_PAR_RESONANCE|" & $ENGINE_PAR_RESONANCE
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|NI_BUS_OFFSET|" & $NI_BUS_OFFSET
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|NI_INSERT_BUS|" & $NI_INSERT_BUS
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|NI_MAIN_BUS|" & $NI_MAIN_BUS
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    @l := "CONST|NI_SEND_BUS|" & $NI_SEND_BUS
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    { Published by NI but never yet compiled here: one line each,
      sentinel first, so an absent one costs only its own line. }
    $v := -999
    $v := $INTMOD_TYPE_ENV_FOLLOW   { RISK-C1 }
    @l := "CONST|INTMOD_TYPE_ENV_FOLLOW|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := -999
    $v := $INTMOD_TYPE_GLIDE   { RISK-C2 }
    @l := "CONST|INTMOD_TYPE_GLIDE|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := -999
    $v := $ENV_TYPE_AHDSR   { RISK-C3 }
    @l := "CONST|ENV_TYPE_AHDSR|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := -999
    $v := $ENV_TYPE_FLEX   { RISK-C4 }
    @l := "CONST|ENV_TYPE_FLEX|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := -999
    $v := $ENV_TYPE_DBD   { RISK-C5 }
    @l := "CONST|ENV_TYPE_DBD|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := -999
    $v := $LFO_TYPE_SINE   { RISK-C6 }
    @l := "CONST|LFO_TYPE_SINE|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := -999
    $v := $LFO_TYPE_RECTANGLE   { RISK-C7 }
    @l := "CONST|LFO_TYPE_RECTANGLE|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := -999
    $v := $LFO_TYPE_TRIANGLE   { RISK-C8 }
    @l := "CONST|LFO_TYPE_TRIANGLE|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := -999
    $v := $LFO_TYPE_SAWTOOTH   { RISK-C9 }
    @l := "CONST|LFO_TYPE_SAWTOOTH|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := -999
    $v := $LFO_TYPE_RANDOM   { RISK-C10 }
    @l := "CONST|LFO_TYPE_RANDOM|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := -999
    $v := $LFO_TYPE_MULTI   { RISK-C11 }
    @l := "CONST|LFO_TYPE_MULTI|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := -999
    $v := $START_CRITERIA_ON_KEY   { RISK-C12 }
    @l := "CONST|START_CRITERIA_ON_KEY|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := -999
    $v := $START_CRITERIA_ON_CONTROLLER   { RISK-C13 }
    @l := "CONST|START_CRITERIA_ON_CONTROLLER|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := -999
    $v := $START_CRITERIA_CYCLE_ROUND_ROBIN   { RISK-C14 }
    @l := "CONST|START_CRITERIA_CYCLE_ROUND_ROBIN|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := -999
    $v := $START_CRITERIA_CYCLE_RANDOM   { RISK-C15 }
    @l := "CONST|START_CRITERIA_CYCLE_RANDOM|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := -999
    $v := $START_CRITERIA_SLICE_TRIGGER   { RISK-C16 }
    @l := "CONST|START_CRITERIA_SLICE_TRIGGER|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $v := -999
    $v := $NI_NOT_FOUND   { RISK-C17 }
    @l := "CONST|NI_NOT_FOUND|" & $v
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    { ---------------------------------------------------------------- }
    { Numeric sweep - the part that needs no names at all.              }
    {                                                                   }
    { An unknown NAME is a compile error that kills the whole script; an
      unknown NUMBER is just a value that reads back as nothing. So the
      last pass walks the ID space directly at three addresses and
      records whatever answers - including parameters NI never
      documented, and the invert-source flag whose published spellings
      both fail to compile.
    
      To identify an unknown ID: change that control in Kontakt's UI,
      re-run, and diff the two reports. The one record that moved is the
      parameter.
    
      Deliberately last, after two saves: if an out-of-range ID upsets
      the engine, everything above is already on disk. }
    { ---------------------------------------------------------------- }
    { -1 is the "no modulator here" address, and the right answer to record if
      group 0 turns out to have no modulators at all. }
    if ($SWEEP = 1)
    $b := -1
    $m := 0
    $ty := 0
    while ($m < $MAX_MODS)
        if ($ty = 0)
            $v := get_engine_par($ENGINE_PAR_INTMOD_TYPE, 0, $m, -1)
            if ($v # $INTMOD_TYPE_NONE)
                $ty := 1
                $b := $m       { first populated modulator slot of group 0 }
            end if
        end if
        inc($m)
    end while

    @l := "SWEEP|mod_slot|" & $b
    if ($n < $MAX_LINES)
        !out[$n] := @l
        inc($n)
    end if

    $i := 0
    $t := 0
    while ($i < $MAX_SWEEP)

        { group level }
        $v := get_engine_par($i, 0, -1, -1)
        @d := ""
        @d := get_engine_par_disp($i, 0, -1, -1)
        @l := "S|group|" & $i & "|" & $v & "|" & @d
        if ($n < $MAX_LINES)
            !out[$n] := @l
            inc($n)
        end if

        { the modulator itself }
        $v := get_engine_par($i, 0, $b, -1)
        @d := ""
        @d := get_engine_par_disp($i, 0, $b, -1)
        @l := "S|mod|" & $i & "|" & $v & "|" & @d
        if ($n < $MAX_LINES)
            !out[$n] := @l
            inc($n)
        end if

        { its first target strip - where the invert flag lives }
        $v := get_engine_par($i, 0, $b, 0)
        @d := ""
        @d := get_engine_par_disp($i, 0, $b, 0)
        @l := "S|target|" & $i & "|" & $v & "|" & @d
        if ($n < $MAX_LINES)
            !out[$n] := @l
            inc($n)
        end if

        { Save as we go. If some ID in this range upsets the engine badly
          enough to stop the callback, everything up to the last save still
          reaches disk - and the last S record on file then names the ID that
          did it, which is worth knowing by itself.
          A counter rather than "mod": the modulo spelling is not on the list
          of constructs this script has actually seen Kontakt accept, and an
          unverified operator is a compile error, not a bad value. }
        inc($t)
        if ($t > 31)
            $t := 0
            !out[$MAX_LINES - 1] := "END|" & $n
            save_array_str(!out, @OUT)
        end if

        inc($i)
    end while

    !out[$MAX_LINES - 1] := "END|" & $n
    save_array_str(!out, @OUT)
    end if

    { ---------------------------------------------------------------- }
    { Final save                                                        }
    { ---------------------------------------------------------------- }
    if ($n < $MAX_LINES)
        !out[$n] := "END|" & $n
        inc($n)
    end if
    !out[$MAX_LINES - 1] := "END|" & $n
    save_array_str(!out, @OUT)

    message("KODA probe: " & $n & " records -> " & @OUT)
end on
